﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;

namespace SharpScript
{
    [Serializable]
    public class HostConfiguration: IComparable, INotifyPropertyChanged
    {
        private List<string>    _additionalAssemblies;
        private List<string>    _additionalNamespaces;
        private SandboxInfo     _sandboxSettings;
        private bool            _debug;
        private bool            _reuseAppDomain;
        private int             _newScriptDomainMemoryThreshhold;

        public HostConfiguration()
        {
            // Use the private backing variables for initialization
            // in order to avoid firing off change notifications.
            _additionalAssemblies = new List<string>();
            _additionalNamespaces = new List<string>();
            _debug = false;
            _newScriptDomainMemoryThreshhold = 0;
            _reuseAppDomain = true;
        }

        /// <summary>
        /// Results in debug IL, symbol, and PDB file generation.
        /// </summary>
        public  Boolean          Debug
        {
            get {
                return _debug;
            }
            set
            {
                if (_debug == value) return;
                _debug = value;
                OnInvokeScriptDomainConfigurationChanged("Debug");
                OnPropertyChanged("Debug");
            }
        }
        /// <summary>
        /// Determines whether the same AppDomain is re-used for each script execution.
        /// </summary>
        public  Boolean          ReuseAppDomain
        {
            get {
                return _reuseAppDomain;
            }
            set
            {
                if (_reuseAppDomain == value) return;
                _reuseAppDomain = value;
                OnPropertyChanged("ReuseAppDomain");
            }
        }
        /// <summary>
        /// Security settings for the AppDomain the scripts will run in.
        /// </summary>
        public  SandboxInfo      SandboxSettings
        {
            get 
            {
                return _sandboxSettings;
            }
            set
            {
                if (_sandboxSettings == value) return;
                _sandboxSettings = value;
                // Bubble up any events raises on the scriptInfo's 
                // properties up to the next responder.
                _sandboxSettings.ScriptDomainConfigurationChanged +=
                    (o, args) => OnInvokeScriptDomainConfigurationChanged(args.SourceProperty);

                OnInvokeScriptDomainConfigurationChanged("Debug");
                OnPropertyChanged("SandboxSettings");
            }
        }
        /// <summary>
        /// Provides an name spaces to be automatically imported.
        /// </summary>
        public  List<string>     AdditionalNamespaces
        {
            get {
                return _additionalNamespaces;
            }
            set
            {
                if (_additionalNamespaces != value)
                {
                    _additionalNamespaces = value;
                    OnInvokeScriptDomainConfigurationChanged("AdditionalNamespaces");
                    OnPropertyChanged("AdditionalNamespaces");
                }
            }
        }
        /// <summary>
        /// An array of paths to assemblies not already provided that are required by the script.
        /// </summary>
        public  List<string>     AdditionalAssemblies
        {
            get {
                return _additionalAssemblies;
            }
            set
            {
                if (_additionalAssemblies != value)
                {
                    _additionalAssemblies = value;
                    OnInvokeScriptDomainConfigurationChanged("AdditionalAssemblies");
                    OnPropertyChanged("AdditionalAssemblies");
                }
            }
        }
        /// <summary>
        /// Creates a new Script Domain when the existing one reaches a certain memory threshhold.
        /// </summary>
        /// <remarks>
        /// This property is ignored when either (i) it is set to zero, or (ii) when ReuseAppDomain is set to False.
        /// </remarks>
        public Int32             NewScriptDomainMemoryThreshhold
        {
            get {
                return _newScriptDomainMemoryThreshhold;
            }
            set
            {
                if (_newScriptDomainMemoryThreshhold != value)
                {
                    _newScriptDomainMemoryThreshhold = value;
                    OnInvokeScriptDomainConfigurationChanged("NewScriptDomainMemoryThreshhold");
                    OnPropertyChanged("NewScriptDomainMemoryThreshhold");
                }
            }
        }


        #region Implementation of IComparable

        /// <summary>
        /// Compares the current instance with another object of the same type and returns an integer 
        /// that indicates whether the current instance precedes, follows, or occurs in the same position 
        /// in the sort order as the other object.
        /// </summary>
        /// <returns>
        /// A value that indicates the relative order of the objects being compared. 
        /// The return value has these meanings:
        /// 
        /// Less than zero      This instance is less than <paramref name="obj"/>. 
        /// Zero                This instance is equal to <paramref name="obj"/>. 
        /// Greater than zero   This instance is greater than <paramref name="obj"/>. 
        /// </returns>
        /// <param name="obj">An object to compare with this instance. </param>
        /// <exception cref="T:System.ArgumentException"><paramref name="obj"/> is not the same type as this instance. </exception>
        /// <filterpriority>2</filterpriority>
        public int CompareTo(object obj)
        {
            var that = (HostConfiguration) obj;

            var value = 0;

            // For properties that already implement IComparable,
            // we'll just use those results.
            value += this.Debug.CompareTo(that.Debug);
            value += this.ReuseAppDomain.CompareTo(that.ReuseAppDomain);
            value += this.SandboxSettings.CompareTo(that.SandboxSettings);

            // Since our collections don't implement IComparable, we'll
            // need to run CompareTo on each item ourselves.

            // If the AdditionalNamespaces are both initalized, then compare them.
            if (this.AdditionalAssemblies != null && that.AdditionalAssemblies != null)
            {
                // If the collections differ, we'll just use the collection size.
                // to decide the matter.
                if (!this.AdditionalAssemblies.Equals(that.AdditionalAssemblies))
                {
                    value += this.AdditionalAssemblies.Count - that.AdditionalAssemblies.Count;
                }
            }
            else // Otherwise, give "credit" to the object that *does* have contents.
            {
                if (this.AdditionalAssemblies != null && that.AdditionalAssemblies == null) value++;
                if (this.AdditionalAssemblies == null && that.AdditionalAssemblies != null) value--;
            }


            // If the AdditionalNamespaces are both initalized, then compare them.
            if (this.AdditionalNamespaces != null && that.AdditionalNamespaces != null)
            {
                // If the collections differ, we'll just use the collection size.
                // to decide the matter.
                if (!this.AdditionalNamespaces.Equals(that.AdditionalNamespaces))
                {
                    value += this.AdditionalNamespaces.Count - that.AdditionalNamespaces.Count;
                }
            }
            else // Otherwise, give "credit" to the object that *does* have contents.
            {
                if (this.AdditionalNamespaces != null && that.AdditionalNamespaces == null) value++;
                if (this.AdditionalNamespaces == null && that.AdditionalNamespaces != null) value--;
            }

            return value;
        }

        #endregion

        #region Implementation of INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(String propertyName)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion


        /// <summary>
        /// Fires when a property is changed that requires the Script Domain to be reloaded.
        /// </summary>
        public event EventHandler<AppDomainSettingsChangedHandlerArgs> ScriptDomainConfigurationChanged;
        private void OnInvokeScriptDomainConfigurationChanged(String message)
        {
            var handler = ScriptDomainConfigurationChanged;
            if (handler != null) handler(this, new AppDomainSettingsChangedHandlerArgs(message));
        }

    }

}